#!/bin/bash
set -euo pipefail

source "$(dirname "$0")/common.sh" || exit 1

require_tool jq
require_tool toml2json

usage_string="Usage: $0 <mdq executable> <path to toml test> [test case grep pattern]"
if [[ "$#" -lt 2 ]]; then
  err 'too few args' "$usage_string"
fi
if [[ "$#" -gt 3 ]]; then
  err 'too many args' "$usage_string"
fi

mdq="$1"
test_file="$2"
pattern="${3:-.*}"

msg debug mdq "$mdq"
msg debug test_file "$test_file"
msg debug pattern "$pattern"

if [[ -x "$mdq" ]]; then
  mdq="$(readlink -f "$mdq")"
  msg debug 'mdq full path' "$mdq"
  msg debug "mdq version" "$("$mdq" --version)"
else
  err 'not executable' "$mdq"
fi

test_json="$(<"$test_file" toml2json)"

function nice_diff() {
  local title="$1"
  local expected="$2"
  local actual="$3"
  if diff --strip-trailing-cr "$expected" "$actual" &>/dev/null ; then
    msg debug "$title" "diff succeeded"
  else
    msg error "$title" "diff failed"
    diff --color=always --strip-trailing-cr -y <(cat <(echo EXPECTED) "$expected") <(cat <(echo ACTUAL) "$actual")
    return 1
  fi
}

format_file_json() {
  local file="$1"
  local as_json
  if as_json="$(jq -S . <(cat "$file"))"; then
    echo "$as_json" > "$file"
  fi
}

function run_test_spec() {
  local spec="$1"
  local spec_name
  spec_name="$(jq -r '.name' <<<"$spec")"
  full_name="$test_file > $spec_name"

  (
    local stdin
    pushd "$(mktemp -d)"

    while read -r md_test_file; do
      local write_to="$md_test_file"
      jq -r '.given.files[$name]' <<<"$spec" --arg name "$md_test_file" > "$write_to"
    done <<<"$(jq -r '.given.files | keys | .[]' <<<"$spec")"

    if jq -e '.expect.ignore' <<<"$spec" &>/dev/null ; then
      msg warning "$full_name" 'skipping test case'
      return 0
    fi

    jq -j '.expect.output' <<<"$spec" >expect_out.txt
    jq -j '.expect.output_err // ""' <<<"$spec" >expect_err.txt
    expect_success="$(jq -r '.expect.expect_success | if . == null then true else . end' <<<"$spec")"
    output_json="$(jq -r '.expect.output_json // false' <<<"$spec")"

    stdin="$(jq -r '.given.md' <<<"$spec")"
    cli_args=()
    while read -r cli_arg ; do
      cli_args+=("$cli_arg")
    done <<<"$(jq -r '.expect.cli_args[]' <<<"$spec")"

    local actual_success=true
    set -x
    "$mdq" <<<"$stdin" "${cli_args[@]}" >actual_out.txt 2>actual_err.txt || actual_success=false
    set +x

    if [[ "$output_json" == true ]]; then
      for file in actual_out.txt actual_err.txt expect_out.txt expect_err.txt ; do
        format_file_json "$file"
      done
    fi

    local any_errors=()
    nice_diff "$full_name: stdout" expect_out.txt actual_out.txt || any_errors+=('stdout')
    nice_diff "$full_name: stderr" expect_err.txt actual_err.txt || any_errors+=('stderr')
    [[ "$expect_success" == "$actual_success" ]] || any_errors+=('exit code')

    if [[ "${#any_errors[@]}" -eq 0 ]]; then
      msg notice "$full_name" 'test passed'
    else
      msg error "$full_name" "test failed due to ${any_errors[*]}"
      exit 1
    fi
  )
}

failures=0
while read -r test_case ; do
  if ! grep -qE "$pattern" <<<"$test_case"; then
    msg debug 'skipping test case' "$test_case"
    continue
  fi
  group "$test_file > $test_case"
  run_test_spec \
    "$(jq '{name: $test_case, given: .given, expect: .expect[$test_case]}' <<<"$test_json" --arg test_case "$test_case")" \
    || failures=$((failures + 1))
  end_group
done <<<"$(jq -r '.expect| keys[]' <<<"$test_json")"

[[ "$failures" -eq 0 ]]
